Conversation
76958b4 to
6fe7d9c
Compare
Greptile SummaryThis PR adds a C++ SDK generator targeting C++20, including Twig templates, a PHP generator class, generated example output, and a full test harness. The second revision addresses a substantial number of previously flagged issues:
Confidence Score: 2/5Not safe to merge — multiple P1 runtime defects remain that will cause crashes and test failures. Three confirmed P1 issues: (1) concurrent coroutine resume UB in Task::get() will crash async callers; (2) Result::error() returns a dangling reference causing UB on every error inspection; (3) distanceEqual(6-arg) produces wrong JSON nesting causing Cpp20Test to fail. The second revision shows strong improvement — many prior P0/P1 findings resolved — but these three defects block a clean merge. templates/cpp/include/base.hpp.twig (Task async semantics, dangling reference), templates/cpp/include/core.hpp.twig (distanceEqual nesting), examples/cpp/include/appwrite/core.hpp (same distanceEqual fix needed in generated output) Important Files Changed
Reviews (75): Last reviewed commit: "fix(cpp): add CMAKE_PREFIX_PATH, paralle..." | Re-trigger Greptile |
9f55656 to
a07a162
Compare
|
@coderabbitai review |
5132d53 to
982469c
Compare
ba19e27 to
6b7d1f0
Compare
…t compound string)
| if: matrix.sdk == 'rust' | ||
| uses: dtolnay/rust-toolchain@1.83.0 | ||
|
|
||
| - name: Setup C++ | ||
| if: matrix.sdk == 'cpp' | ||
| run: | | ||
| sudo apt-get update | ||
| sudo apt-get install -y build-essential cmake libcurl4-openssl-dev nlohmann-json3-dev libgtest-dev | ||
| git clone --depth 1 --branch 1.10.5 https://github.com/libcpr/cpr.git /tmp/cpr |
There was a problem hiding this comment.
cmake configure step missing -DCMAKE_PREFIX_PATH — cpr may not be found
cpr is installed to /usr/local via cmake --target install, but the subsequent build step runs cmake -S . -B build -DAPPWRITE_BUILD_TESTS=ON without -DCMAKE_PREFIX_PATH=/usr/local. On Ubuntu runners CMAKE_PREFIX_PATH defaults to /usr only, so find_package(cpr QUIET) will fail and FetchContent_MakeAvailable(cpr) will attempt a network clone — adding a GitHub dependency and ~5 min of cpr+libcurl compilation to every CI run. Add the prefix:
cpp)
cmake -S . -B build -DAPPWRITE_BUILD_TESTS=ON -DCMAKE_PREFIX_PATH=/usr/local
cmake --build build -j$(nproc)
ctest --test-dir build --output-on-failure
;;b76ed09 to
c846744
Compare
| struct promise_type { | ||
| Task get_return_object() { return Task{std::coroutine_handle<promise_type>::from_promise(*this)}; } | ||
| std::suspend_always initial_suspend() noexcept { return {}; } | ||
| std::suspend_always final_suspend() noexcept { return {}; } | ||
| void unhandled_exception() { result = std::current_exception(); } | ||
| template<typename V> | ||
| void return_value(V&& value) { result = std::variant<T, std::exception_ptr>(std::forward<V>(value)); } | ||
| std::optional<std::variant<T, std::exception_ptr>> result; | ||
| }; | ||
|
|
||
| explicit Task(std::coroutine_handle<promise_type> h) : handle(h) {} | ||
| ~Task() { if (handle) handle.destroy(); } | ||
| Task(Task&& other) noexcept : handle(std::exchange(other.handle, nullptr)) {} | ||
| T get() { | ||
| if (handle && !handle.done()) handle.resume(); |
There was a problem hiding this comment.
Task<T>::get() spin loop resumes the coroutine handle twice — undefined behaviour
The spin loop calls handle.resume() which runs the coroutine until it suspends at co_await *pool. ThreadPoolAwaiter::await_suspend then enqueues a second h.resume() on the pool and returns — the coroutine is now suspended with a pool-queued resumption pending. The spin loop's !handle.done() check is true, so it calls handle.resume() again immediately. Two concurrent coroutine_handle::resume() invocations on the same handle are undefined behaviour per C++20 [dcl.fct.def.coroutine]. The pool worker's call races with the spin call → likely crash.
Fix: store a std::promise/std::future inside promise_type and block on the future after a single initial resume(), letting the pool be the sole entity calling resume() after first suspension.
| T& operator*() { return value(); } | ||
|
|
||
| const T& value() const { | ||
| if (isErr()) std::rethrow_exception(std::get<std::exception_ptr>(data_)); | ||
| return std::get<T>(data_); | ||
| } | ||
|
|
||
| T& value() { | ||
| if (isErr()) std::rethrow_exception(std::get<std::exception_ptr>(data_)); |
There was a problem hiding this comment.
error() returns a dangling reference — UB on every call
std::rethrow_exception throws the stored exception into the local catch clause. In C++, the caught-exception object lives only until the handler exits; returning e by const& from inside that handler gives the caller a reference to a destroyed object. Every access to the returned reference is undefined behaviour.
// Current (UB):
catch (const AppwriteException& e) { return e; } // e is destroyed when catch exits
// Fix — return by value:
std::shared_ptr<AppwriteException> error() const {
if (isOk()) throw AppwriteException("Result is Ok, no error available");
try { std::rethrow_exception(std::get<std::exception_ptr>(data_)); }
catch (const AppwriteException& e) { return e.clone(); }
return std::make_shared<AppwriteException>("Unknown error");
}| void await_suspend(std::coroutine_handle<> h) noexcept { | ||
| task_.handle.resume(); | ||
| h.resume(); | ||
| } | ||
| T await_resume() { | ||
| auto& res = *task_.handle.promise().result; | ||
| if (res.index() == 1) std::rethrow_exception(std::get<1>(res)); | ||
| return std::move(std::get<0>(res)); | ||
| } | ||
| }; | ||
| // Note: Task is move-only. After co_await, the original Task handle is null. |
There was a problem hiding this comment.
co_await task blocks the calling thread — advertised coroutine composability is synchronous in disguise
await_suspend calls task_.handle.resume(), which runs makeResolvedTask until it hits co_return f->get(). f->get() is a blocking call that waits for the thread pool to finish. handle.resume() does not return until the pool completes — the outer coroutine's thread is held the entire time. co_await callAsync(...) is therefore identical in cost and blocking behaviour to calling .get() directly. A non-blocking fix requires await_suspend to enqueue the outer coroutine's handle onto the pool rather than calling handle.resume() inline.
What does this PR do?
Adds a modern, production-ready C++ SDK generator targeting the C++20 standard.
This implementation focuses on high performance, thread-safety, and a seamless developer
experience through a monolithic header-only architecture and coroutine-compatible async dispatch.
Key Features
include/appwrite/appwrite.hppentry point. No separate compilation units required.ThreadPool, enabling concurrent execution across multiple threads. TheTask<T>type provides a coroutine-compatible interface (co_await/.get()). Note: true non-blocking I/O would require an async runtime (Asio/libuv), which is intentionally out of scope for this SDK.callBytes), and complex JSON serialization.Clientuses a copy-on-write config pattern viashared_ptr<const Config>with mutex protection, making it safe for concurrent use across multiple threads.Production Hardening
InputFile::Progress(C++20 compliant, order-safe).verify()— extracts the namespaced"type"field from error JSON and passes it toServerExceptionfor richer error context.makeResolvedTask— exceptions captured bystd::futureare caught and returned asResult<T>::Errrather than propagating as raw exceptions.[]suffix (e.g.k[]=v1&k[]=v2).Test Plan
x-appwrite-idpropagation.Query,Permission, andIDhelper correctness.test.shexecutes the suite within themockapiDocker network. The build-validation CI step compiles headers without running integration tests, keeping CI fast.Verified output (condensed):